import pandas as pd
import numpy as np
from sklearn.decomposition import PCA  # 导入sklearn库中的主成分分析模块，用于对数据进行降维处理，提取主成分
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA  # 导入线性判别分析模块，借助类别信息对数据进行降维
import matplotlib.pyplot as plt  # 导入绘图库，用于可视化展示降维后的数据分布情况

# 函数功能：从给定的URL地址加载数据集，并依据提供的列名对数据进行命名，最终返回处理后的数据
# 参数说明：
# - url (str)：数据集所在的URL地址，指向一个CSV格式的文件。
# - columns (list)：一个包含字符串的列表，用于为读取的数据集中的各列指定名称。
# 返回值说明：
# - 如果数据加载成功，返回一个DataFrame类型的数据对象，包含按照指定列名命名好的数据；若加载过程中出现异常，则返回None。
def load_data(url, columns):
    try:
        # 使用pandas库的read_csv函数读取CSV文件，设置header=None表示文件中无标题行，
        # 通过names参数传入列名列表，为读取的数据赋予相应的列名
        data = pd.read_csv(url, header=None, names=columns)
    except Exception as e:
        # 捕获在数据读取过程中出现的任何异常，并打印详细的错误信息，方便排查问题
        print(f"加载数据时出错: {e}")
        return None
    return data

# 函数功能：绘制降维后的数据散点图，通过散点的颜色区分不同类别，同时添加图表标题、坐标轴标签以及颜色条等元素，便于直观查看数据分布和类别关系
# 参数说明：
# - X_reduced (numpy.ndarray)：经过降维处理后的数据，其形状为 (样本数量, 降维后的维度)，例如二维数据形状为 (n, 2)，一维数据形状为 (n, 1)。
# - y (numpy.ndarray 或 pd.Series)：与降维数据对应的类别标签，用于确定散点的颜色，以直观展示不同类别在降维空间中的分布情况。
# - title (str)：图表的标题，用于简要描述该图所展示的降维方法及相关内容，帮助读者快速理解图表含义。
# - labels (tuple, 可选)：一个包含两个字符串的元组，默认值为 ('第一主成分', '第二主成分')，分别用于设置x轴和y轴的标签；
#                         当处理一维降维数据时，可按需修改第二个元素为空字符串等合适的值。
def plot_reduction(X_reduced, y, title, labels=('第一主成分', '第二主成分')):
    plt.figure(figsize=(8, 6))  # 创建一个新的图形对象，并设置图形的大小，这里设置为宽度8单位、高度6单位，便于展示数据

    # 根据降维后的数据维度来绘制散点图。如果降维后数据是二维（即列数大于1），则正常取第一列作为x坐标，第二列作为y坐标绘制散点；
    # 如果是一维数据（列数等于1），则将y轴坐标统一设置为0，仅以x轴展示降维后的数据情况
    if X_reduced.shape[1] > 1:
        scatter = plt.scatter(X_reduced[:, 0], X_reduced[:, 1],
                              c=y, cmap='viridis', edgecolor='k', s=50)
    else:
        scatter = plt.scatter(X_reduced[:, 0], np.zeros(len(X_reduced)),
                              c=y, cmap='viridis', edgecolor='k', s=50)

    plt.title(title)  # 设置图表的标题，使其清晰反映图表所展示的降维相关信息
    plt.xlabel(labels[0])  # 设置x轴的标签，通常描述降维后的某个维度信息或对应的成分名称
    plt.ylabel(labels[1])  # 设置y轴的标签，同理，对应另一个维度信息或成分名称（对于一维情况可能为空等合适表示）
    plt.colorbar(scatter, label='类别标签')  # 添加颜色条，用于直观展示不同颜色所代表的类别，方便查看数据按类别分布情况
    plt.grid(True)  # 在图表中显示网格线，有助于更清晰地观察数据点的分布规律和相对位置关系
    plt.show()  # 显示绘制好的图形，将图表呈现给用户查看

# 加载红酒数据集
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data"
columns = ['Class', 'Alcohol', 'Malic_acid', 'Ash', 'Alcalinity_of_ash', 'Magnesium',
           'Total_phenols', 'Flavanoids', 'Nonflavanoid_phenols', 'Proanthocyanins',
           'Color_intensity', 'Hue', 'OD280/OD315', 'Proline']
data = load_data(url, columns)

if data is not None:
    # 筛选出类别为1和2的数据子集，目的是简化后续分析场景，聚焦在这两类数据上进行降维操作及可视化展示，方便对比和观察
    filtered_data = data[data['Class'].isin([1, 2])]
    # 提取特征数据，即从筛选后的数据中获取除了'Class'（类别列）之外的所有列，这些列的数据将作为后续降维的输入特征
    X = filtered_data.iloc[:, 1:]
    # 提取类别标签，单独获取筛选后数据中的'Class'列，用于在绘制散点图时通过颜色区分不同类别，以及在LDA降维时提供类别信息
    y = filtered_data['Class']

    # PCA - 将特征降维到二维
    pca = PCA(n_components=2)  # 创建PCA对象，指定要将数据降维到的主成分数量为2个，即提取两个维度来表示原始数据的主要特征信息
    X_pca = pca.fit_transform(X)  # 使用PCA对象对特征数据进行拟合，并将其转换到二维空间，得到降维后的二维数据
    print("PCA将特征降维到两个维度：")
    print(X_pca)

    # 绘制PCA结果
    plot_reduction(X_pca, y, 'PCA: 特征降维到二维')

    # LDA - 将特征降维到一维（对于两类问题，LDA降维后只能为一维）
    lda = LDA(n_components=1)  # 创建LDA对象，由于当前是对两类数据进行处理，按照LDA的特性，设置降维后的维度为1个
    X_lda = lda.fit_transform(X, y)  # 使用LDA对象对特征数据和对应的类别标签进行拟合，并将特征数据降维到一维空间，利用了类别信息辅助降维
    print("LDA将特征降维到一个维度：")
    print(X_lda)

    # 绘制LDA结果
    plot_reduction(X_lda, y, 'LDA: 特征降维到一维', labels=('线性判别成分', ''))